﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using RayDen.Library.Core.Components;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace RayDen.Library.Components.Surface
{
    [Serializable]
    public class SurfaceMaterial : IEnumerable<SurfaceBsdf>, ISurfaceMaterial, IStringSerializable
    {

        public static bool Has(BrdfType val, BrdfType f)
        {
            //return val.HasFlag(f);
            return ((uint)val & (uint)f).Equals((uint)f);
        }

        public const int MaxBrdf = 4;
        public string Name { get; set; }
        public BrdfType Type { get; set; }

        public BrdfClass Class { get; set; }
        public TextureInfo alphaTexture;

        public TextureInfo AlphaTexture { get { return this.alphaTexture; } }
        public bool TransparentShadows { get; set; }

        public bool IsDiffuse()
        {
            return First.IsDiffuse();
            //return Has( Type, BrdfType.Diffuse );
        }

        public float ContinuationProbability { get { return this.First.ContinuationProb; } }

        public float Pdf(ref Vector wo, ref Vector wi)
        {
            return this.First.Pdf(ref wo, ref wi, BxDFTypes.BSDF_ALL);
        }

        public bool IsSpecular()
        {
            return Has(Type, BrdfType.Specular) || Has(Type, BrdfType.Glossy);
        }

        public bool IsRefractive()
        {
            return Has(Type, BrdfType.Refractive); ;
        }

        public SurfaceBsdf First
        {
            get { return this.brdfs[0]; }
        }

        protected SurfaceBsdf[] brdfs;
        protected float[] brdfWeights;

        [NonSerialized]
        protected float defaultWeight;

        public SurfaceMaterial()
        {
            brdfs = new SurfaceBsdf[MaxBrdf];
            brdfWeights = new float[MaxBrdf];
            defaultWeight = 1f / MaxBrdf;
        }

        public SurfaceMaterial(SurfaceBsdf bsdf)
        {
            brdfs = new[] { bsdf };
            brdfWeights = new float[MaxBrdf];
            defaultWeight = 1f;
        }

        public SurfaceMaterial(params SurfaceBsdf[] brdfs)
        {
            this.brdfs = brdfs;
            defaultWeight = 1f / (float)brdfs.Length;
            brdfWeights = brdfs.Select(i => defaultWeight).ToArray();
        }

        public void Add(SurfaceBsdf brdf)
        {
            for (int i = 0; i < MaxBrdf; i++)
            {
                if (brdfs[i] == null)
                {
                    brdfs[i] = brdf;
                    defaultWeight = 1f / (float)brdfs.Count(b => b != null);
                    brdfWeights = brdfs.Where(b => b != null).Select(b => defaultWeight).ToArray();
                    return;
                }
            }
            this.brdfs = brdfs.Union(new[] { brdf }).ToArray();
            defaultWeight = 1f / (float)brdfs.Count(b => b != null);
            brdfWeights = brdfs.Where(b => b != null).Select(b => defaultWeight).ToArray();
        }

        public SurfaceBsdf Sample(float u0)
        {
            var rnd = Math.Round(u0 * (brdfs.Length - 1));
            return this.brdfs[(int)rnd];
        }

        public void f(ref Vector wo, ref Vector wi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs, BrdfType types = BrdfType.Diffuse)
        {
            if (brdfs.Length == 1)
            {
                if (brdfs[0].IsSpecular())
                {
                    fs = new RgbSpectrum(0f);
                }
                else
                    brdfs[0].f(ref wo, ref wi, ref N, ref in_fs, out fs);
                return;
            }

            var res = new RgbSpectrum();
            fs = new RgbSpectrum();
            foreach (var surfaceBsdf in brdfs)
            {
                if (Has(surfaceBsdf.Type, types))
                {
                    surfaceBsdf.f(ref wo, ref wi, ref N, ref in_fs, out res);
                    fs += res;
                }
            }
        }

        public virtual RgbSpectrum Sample_f(ref Vector wo, out Vector wi, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_fs, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out float pdf, out BsdfEvent bsdfEvent)
        {
            var res = new RgbSpectrum();
            pdf = 0f;
            bsdfEvent = BsdfEvent.None;
            wi = new Vector();
            SurfaceBsdf bsdf;
            if (this.brdfs.Length == 1)
            {
                bsdf = this.brdfs[0];
            }
            else
                bsdf = Sample(u2);

            if (bsdf != null)
            {
                BsdfSampleData bd;
                bsdf.Sample_f(ref wo, ref N, ref shadeN, ref in_fs, u0, u1, u2, ref surfaceData, out bd);
                bsdfEvent = bsdf.Type.TypeToEvent();
                pdf = bd.Pdf*brdfs.Length;
                wi = bd.Wi;
                res = bd.F;
            }

#if DEBUG
            Assert.IsTrue(!wi.IsZero());
            Assert.IsTrue(!float.IsNaN(pdf), throwException:false);
#endif
            return res;
        }

        public virtual RgbSpectrum Sample_f(ref Vector wo, out Vector wi, ref Normal N, ref Normal shadeN,
            ref RgbSpectrum in_fs, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out float pdf,
            out bool specularBounce)
        {
            BsdfEvent evt;
            var samp = this.Sample_f(ref wo, out wi, ref N, ref shadeN, ref in_fs, u0, u1, u2, ref surfaceData, out pdf, out evt);
            specularBounce = evt.Has(BsdfEvent.Specular);
            return samp;
        }
        public IEnumerator<SurfaceBsdf> GetEnumerator()
        {
            return brdfs.ToList().GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return brdfs.GetEnumerator();
        }

        public string SerializeToString()
        {
            throw new NotImplementedException();
        }

        public void FromString(string data)
        {
            throw new NotImplementedException();
        }
    }


    public class RoughMatte : SurfaceMaterial
    {

        public RoughMatte(ref RgbSpectrum diff)
        {
            this.Type = BrdfType.Diffuse;
            this.Add(new OrenNayarMaterial(diff, diff.Filter()));
        }


        public override RgbSpectrum Sample_f(ref Vector wo, out Vector wi, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_fs, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out float pdf, out BsdfEvent bsdfEvent)
        {
            BsdfSampleData bsdfSample;
            var on = new OrenNayarMaterial(surfaceData.Diffuse, surfaceData.Roughness.Filter());
            on.Sample_f(ref wo, ref N, ref shadeN, ref in_fs, u0, u1, u2, ref surfaceData, out bsdfSample);
            bsdfEvent = BsdfEvent.Diffuse | BsdfEvent.Reflect;

            pdf = bsdfSample.Pdf;
            wi = bsdfSample.Wi;
            return bsdfSample.F;
        }

    }
}